package com.sky.utils;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author majun
 * @package com.sky.utils
 * @date 2025/6/21 03:03
 */
@Slf4j
@Component
public class BitifulS3Util {

    private final S3Client s3Client;
    private final S3Presigner s3Presigner;
    private final String bucketName;
    private final String rootDir;
    private final String customDomain;

    /**
     * 构造函数，注入 SDK 客户端和配置
     *
     * @param s3Client    S3 客户端
     * @param s3Presigner 预签名 URL 生成器
     * @param props       自定义配置类，包含 bucket 和 rootDir
     */
    public BitifulS3Util(S3Client s3Client, S3Presigner s3Presigner, com.sky.properties.BitifulProperties props) {
        this.s3Client = s3Client;
        this.s3Presigner = s3Presigner;
        this.bucketName = props.getBucket();
        this.customDomain = props.getCustomDomain();

        String dir = props.getRootDir();
        if (!StringUtils.hasText(dir)) {
            this.rootDir = "";
        } else {
            this.rootDir = dir.endsWith("/") ? dir : dir + "/";
        }

    }

    /**
     * 拼接完整的对象 Key，自动加根目录前缀
     *
     * @param key 业务传入的相对路径 Key
     * @return 完整的存储 Key，包含根目录
     */
    private String buildKey(String key) {
        if (key.startsWith("/")) {
            key = key.substring(1);
        }
        return rootDir + key;
    }

    /**
     * 上传 MultipartFile 文件（来自 Spring MVC 的文件上传）
     *
     * @param key  对象存储路径（相对路径）
     * @param file MultipartFile 文件
     * @throws IOException 读取文件异常
     */
    public void uploadFile(String key, MultipartFile file) throws IOException {
        String fullKey = buildKey(key);
        
        log.info("开始上传文件到S3，Key={}, 文件大小={} bytes", fullKey, file.getSize());

        try {
            PutObjectRequest putReq = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(fullKey)
                    .build();

            // 转成字节上传
            s3Client.putObject(putReq, RequestBody.fromBytes(file.getBytes()));
            
            log.info("文件上传成功，Key={}", fullKey);
        } catch (S3Exception e) {
            log.error("S3上传失败，Key={}, 错误码={}, 错误信息={}", fullKey, e.awsErrorDetails().errorCode(), e.awsErrorDetails().errorMessage());
            throw new IOException("S3上传失败: " + e.awsErrorDetails().errorMessage(), e);
        } catch (Exception e) {
            log.error("文件上传异常，Key={}", fullKey, e);
            throw new IOException("文件上传异常", e);
        }
    }

    /**
     * 上传本地文件
     *
     * @param key      对象存储路径（相对路径）
     * @param filePath 本地文件绝对路径
     */
    public void uploadFile(String key, String filePath) {
        String fullKey = buildKey(key);
        
        log.info("开始上传本地文件到S3，Key={}, 文件路径={}", fullKey, filePath);

        try {
            PutObjectRequest putReq = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(fullKey)
                    .build();

            s3Client.putObject(putReq, RequestBody.fromFile(new java.io.File(filePath)));
            
            log.info("本地文件上传成功，Key={}", fullKey);
        } catch (S3Exception e) {
            log.error("S3上传失败，Key={}, 错误码={}, 错误信息={}", fullKey, e.awsErrorDetails().errorCode(), e.awsErrorDetails().errorMessage());
            throw new RuntimeException("S3上传失败: " + e.awsErrorDetails().errorMessage(), e);
        } catch (Exception e) {
            log.error("本地文件上传异常，Key={}", fullKey, e);
            throw new RuntimeException("本地文件上传异常", e);
        }
    }

    /**
     * 删除文件
     *
     * @param key 对象存储路径（相对路径）
     */
    public void deleteFile(String key) {
        String fullKey = buildKey(key);
        
        log.info("开始删除S3文件，Key={}", fullKey);

        try {
            DeleteObjectRequest delReq = DeleteObjectRequest.builder()
                    .bucket(bucketName)
                    .key(fullKey)
                    .build();

            s3Client.deleteObject(delReq);
            
            log.info("文件删除成功，Key={}", fullKey);
        } catch (S3Exception e) {
            log.error("S3删除失败，Key={}, 错误码={}, 错误信息={}", fullKey, e.awsErrorDetails().errorCode(), e.awsErrorDetails().errorMessage());
            throw new RuntimeException("S3删除失败: " + e.awsErrorDetails().errorMessage(), e);
        } catch (Exception e) {
            log.error("文件删除异常，Key={}", fullKey, e);
            throw new RuntimeException("文件删除异常", e);
        }
    }

    /**
     * 列出根目录下所有文件，返回相对路径列表
     *
     * @return 文件相对路径列表
     */
    public List<String> listFiles() {
        log.info("开始列出S3文件，根目录={}", rootDir);

        try {
            ListObjectsRequest listReq = ListObjectsRequest.builder()
                    .bucket(bucketName)
                    .prefix(rootDir) // 只列出指定根目录的文件
                    .build();

            ListObjectsResponse resp = s3Client.listObjects(listReq);

            List<String> files = resp.contents().stream()
                    .map(obj -> obj.key().substring(rootDir.length())) // 去掉根目录前缀
                    .collect(Collectors.toList());
            
            log.info("列出文件成功，共{}个文件", files.size());
            return files;
        } catch (S3Exception e) {
            log.error("S3列出文件失败，错误码={}, 错误信息={}", e.awsErrorDetails().errorCode(), e.awsErrorDetails().errorMessage());
            throw new RuntimeException("S3列出文件失败: " + e.awsErrorDetails().errorMessage(), e);
        } catch (Exception e) {
            log.error("列出文件异常", e);
            throw new RuntimeException("列出文件异常", e);
        }
    }

    /**
     * 生成指定文件的预签名下载链接
     *
     * @param key    对象存储路径（相对路径）
     * @param expire 过期时间，单位秒
     * @return 预签名URL字符串
     */
    public String generatePresignedUrl(String key, long expire) {
        String fullKey = buildKey(key);
        
        log.info("开始生成预签名URL，Key={}, 过期时间={}秒", fullKey, expire);

        try {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(bucketName)
                    .key(fullKey)
                    .build();

            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofSeconds(expire))
                    .getObjectRequest(getObjectRequest)
                    .build();

            String url = s3Presigner.presignGetObject(presignRequest).url().toString();
            log.info("预签名URL生成成功，Key={}", fullKey);
            return url;
        } catch (S3Exception e) {
            log.error("S3生成预签名URL失败，Key={}, 错误码={}, 错误信息={}", fullKey, e.awsErrorDetails().errorCode(), e.awsErrorDetails().errorMessage());
            throw new RuntimeException("S3生成预签名URL失败: " + e.awsErrorDetails().errorMessage(), e);
        } catch (Exception e) {
            log.error("生成预签名URL异常，Key={}", fullKey, e);
            throw new RuntimeException("生成预签名URL异常", e);
        }
    }

    /**
     * 生成使用自定义域名的文件访问URL
     *
     * @param key 对象存储路径（相对路径）
     * @return 自定义域名访问URL
     */
    public String generateCustomDomainUrl(String key) {
        String fullKey = buildKey(key);
        // 使用自定义域名，不需要拼接桶名
        String url = customDomain + "/" + fullKey;
        log.info("生成自定义域名URL，Key={}, URL={}", fullKey, url);
        return url;
    }
}
